package com.jzo2o.foundations.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.foundations.constants.RedisConstants;
import com.jzo2o.foundations.mapper.*;
import com.jzo2o.foundations.model.domain.*;
import com.jzo2o.foundations.model.dto.request.*;
import com.jzo2o.foundations.model.dto.response.*;
import com.jzo2o.foundations.service.IServeService;
import com.jzo2o.mysql.utils.PageHelperUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.TotalHits;
import org.checkerframework.checker.units.qual.A;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务表 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-07-03
 */
@Slf4j
@Service
public class ServeServiceImpl extends ServiceImpl<ServeMapper, Serve> implements IServeService<A> {

    private final ServeMapper serveMapper;

    @Autowired
    private ServeItemMapper serveItemMapper;

    @Autowired
    private RegionMapper regionMapper;

    @Autowired
    private ServeTypeMapper serveTypeMapper;

    @Autowired
    private ServeSyncMapper serveSyncMapper;

    @Autowired
    private RestHighLevelClient esClient;

    public ServeServiceImpl(ServeMapper serveMapper) {
        this.serveMapper = serveMapper;
    }

    //分页查询
    @Override
    public PageResult<ServeResDTO> findByPage(ServePageQueryReqDTO servePageQueryReqDTO) {

        return PageHelperUtils.selectPage(servePageQueryReqDTO,
                ()->baseMapper.queryListByRegionId(servePageQueryReqDTO.getRegionId()));
    }

    //批量保存服务
    @Override
    @Transactional(rollbackFor = Exception.class)//涉及多张表需要用事务保证原子性
    public void batchSave(List<ServeUpsertReqDTO> serveUpsertReqDTOList) {
        /**
         * 服务项目必须是启用状态，才能添加
         *         一个项目只能添加一次
         *         思路：新增项目保存到serve表中，需要补充城市编码参数，其余参数不需要管
         *         1，根据服务项目id去serve-item表中查询启用状态active-status，不为2则返回异常
         *         2，根据城市id，和项目id查询该城市下此时是否已经有该项目，返回一个数量值，有则返回异常，查询serve表
         */
        for (ServeUpsertReqDTO serveUpsertReqDTO : serveUpsertReqDTOList) {
            //1,去服务项目表中查询状态值
            ServeItem serveItem = serveItemMapper.selectById(serveUpsertReqDTO.getServeItemId());
            if(ObjectUtil.isNull(serveItem)||serveItem.getActiveStatus()!=2){
                throw new ForbiddenOperationException("服务项未启用");
            }
            //2,根据城市id，和项目id查询该城市下此时是否已经有该项目
            Integer count = serveMapper.selectCount(Wrappers.<Serve>lambdaQuery()
                    .eq(Serve::getRegionId, serveUpsertReqDTO.getRegionId())
                    .eq(Serve::getServeItemId, serveUpsertReqDTO.getServeItemId()));
            if(count>0){
                throw new ForbiddenOperationException("该城市下已存在该项目");
            }
            //满足条件，此时可以插入数据
               //hutool提供的方法：BeanUtil.copyProperties(serveUpsertReqDTO,Serve.class);
            Serve serve = new Serve();
            BeanUtils.copyProperties(serveUpsertReqDTO,serve);

            //补充未传递字段，这里主要是城市编码
            Region region = regionMapper.selectById(serveUpsertReqDTO.getRegionId());
            if (ObjectUtil.isNotNull( region)) {
                serve.setCityCode(region.getCityCode());
            }
            baseMapper.insert(serve);
        }

    }

    //更新价格-serve表(同步sync表维护)
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updatePriceById(Long id, BigDecimal price) {
        Serve serve = new Serve();
        serve.setPrice(price);
        serve.setId(id);
        baseMapper.updateById(serve);
        //同步synv表
        ServeSync serveSync = serveSyncMapper.selectById(id);
        serveSync.setPrice(serve.getPrice());
        serveSyncMapper.updateById(serveSync);
    }

    //删除区域服务
    @Override
    public boolean removeByIdAndStatus(Long id) {
        Serve serve = baseMapper.selectById(id);
        if(serve.getSaleStatus()==0){
            baseMapper.deleteById( id);
            return true;
        }
        else if(ObjectUtil.isNull(serve)||serve.getSaleStatus()!=0){
            throw new ForbiddenOperationException("该服务正在销售中，无法删除");

        }
        return false;
    }

    //设置上架-区域服务上架
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void onSale(Long id) {
        /**
         * 步骤：1，当前区域服务为未上架状态-status不为2
         *      2，在服务项目表中服务项目是启用状态，状态为2
         */
        Serve serve = baseMapper.selectById(id);
        if(ObjectUtil.isNull(serve)||serve.getSaleStatus()==2){
            throw new ForbiddenOperationException("该服务不可上架");
        }
        ServeItem serveItem = serveItemMapper.selectById(serve.getServeItemId());
        if(ObjectUtil.isNull(serveItem)||serveItem.getActiveStatus()!=2){
            throw new ForbiddenOperationException("该服务项未启用");
        }
        serve.setSaleStatus(2);
        baseMapper.updateById(serve);
        //同步表（sync）更新-暂时还没有往es表里同步
        addServeSync(serve);
    }

    //同步表更新方法（添加一条数据）
    private void addServeSync(Serve serve) {
        //先拿到相关的服务表，服务类型表，服务项目表（同步表的更新依赖于其余三张表），已有serve表
        ServeItem serveItem = serveItemMapper.selectById(serve.getServeItemId());
        ServeType serveType = serveTypeMapper.selectById(serveItem.getServeTypeId());
        //填充同步表字段
        ServeSync serveSync = new ServeSync();
        serveSync.setId(serve.getId());
        serveSync.setServeItemName(serveItem.getName());
        serveSync.setServeItemId(serveItem.getId());
        serveSync.setServeTypeId(serveType.getId());
        serveSync.setServeTypeName(serveType.getName());
        serveSync.setServeTypeIcon(serveType.getServeTypeIcon());
        serveSync.setServeTypeImg(serveType.getImg());
        serveSync.setPrice(serve.getPrice());
        serveSync.setIsHot(serve.getIsHot());
        serveSync.setHotTimeStamp(serve.getHotTimeStamp());
        serveSync.setServeItemSortNum(serveItem.getSortNum());
        serveSync.setServeTypeSortNum(serveType.getSortNum());
        serveSync.setDetailImg(serveItem.getDetailImg());
        serveSync.setServeItemIcon(serveItem.getServeItemIcon());
        serveSync.setServeItemImg(serveItem.getImg());
        serveSync.setCityCode(serve.getCityCode());
        serveSync.setUnit(serveItem.getUnit());
        //插入同步表
        serveSyncMapper.insert(serveSync);
        
    }

    //设置下架
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void offSale(Long id) {
        //思路：1，当前区域服务为已上架状态-status为2（serve表）
        Serve serve = baseMapper.selectById(id);
        if(ObjectUtil.isNull( serve)||serve.getSaleStatus()!=2){
            throw new ForbiddenOperationException("该服务不可下架");
        }
        serve.setSaleStatus(1);
        baseMapper.updateById( serve);
        //同步表中删除本条数据
        serveSyncMapper.deleteById(id);
    }

    //设置热门
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void onHot(Long id) {
        //设置热门字段的必要条件是当前状态为0，需要修改为1
        Serve serve = baseMapper.selectById(id);
        if(ObjectUtil.isNull(serve)||serve.getIsHot()!=1){
            throw new ForbiddenOperationException("该服务不可设为热门");
        }
        serve.setIsHot(1);
        baseMapper.updateById(serve);
        //同步修改sync表
        ServeSync serveSync = serveSyncMapper.selectById(id);
        serveSync.setIsHot(serve.getIsHot());
        serveSyncMapper.updateById(serveSync);
    }

    //取消热门
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void offHot(Long id) {
        //思路：取消热门的必要条件是当前状态为1-热门状态，需要设置为0
        Serve serve = baseMapper.selectById(id);
        if(ObjectUtil.isNull(serve)||serve.getIsHot()!=1){
            throw new ForbiddenOperationException("该服务不可取消热门");
        }
        serve.setIsHot(0);
        baseMapper.updateById(serve);
        //数据同步到sync表
        ServeSync serveSync = serveSyncMapper.selectById(id);
        serveSync.setIsHot(serve.getIsHot());
        serveSyncMapper.updateById(serveSync);
    }

    //todo 需要整理：重点mapper集合映射方式，集合截取，集合返回空集合的工具类
    @Caching(cacheable = {
            //在查询是要考虑缓存穿透，为避免缓存传统加条件，查询结果为空时（0）只缓存30分钟
            @Cacheable(value = RedisConstants.CacheName.SERVE_ICON,
                    key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES,
                    unless = "#result.size()>0"),
            //查询结果大于零时永久缓存
            @Cacheable(value = RedisConstants.CacheName.SERVE_ICON,
                    key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.FOREVER,
                    unless = "#result.size()==0")
    })
    @Override
    public List<ServeCategoryResDTO> firstPageServeList(Long regionId) {
        /**
         * 思路：首页展示服务首先根据区域id查询列表，然后截取展示几个列表
         * 主要步骤：1，校验区域是否启用
         *         2，查询服务列表
         *         3，截取列表
         */
        //校验区域状态以及是否为空
        Region region = regionMapper.selectById(regionId);
        if(ObjectUtil.isNull( region)||region.getActiveStatus()!=2){
            return Collections.emptyList();
        }
        //查询服务列表
        List<ServeCategoryResDTO> list = baseMapper.findListByRegionId(regionId);
        if(ObjectUtil.isNull( list)){
            return Collections.emptyList();
        }
        //截取列表
         list = CollUtil.sub(list, 0, Math.min(list.size(), 2));
        list.forEach(l->{
            List<ServeSimpleResDTO> list1 = l.getServeResDTOList();
            list1=CollUtil.sub(list1,0,Math.min(list1.size(),4));
        });
        return list;
    }

    @Caching(cacheable = {
            //考虑缓存穿透，返回结果有值时永久缓存
            @Cacheable(value = RedisConstants.CacheName.HOT_SERVE,
            key = "#regionId",
            cacheManager = RedisConstants.CacheManager.FOREVER,
            unless = "#result.size()<=0"),
            //为空时只缓存30分钟
            @Cacheable(value = RedisConstants.CacheName.HOT_SERVE,
                    key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES,
                    unless = "#result.size()>0")})
    @Override
    public List<ServeAggregationSimpleResDTO> hotServeList(Long regionId) {
        //注意先校验区域是否启用
        Region region = regionMapper.selectById(regionId);
        if(ObjectUtil.isNull( region)||region.getActiveStatus()!=2){
            return Collections.emptyList();
        }
        List<ServeAggregationSimpleResDTO> list=baseMapper.hotServeList(regionId);
        return list;
    }

    @Override
    public ServeAggregationSimpleResDTO findServeDetailById(Long id) {
        //思路：根据id查询
        ServeAggregationSimpleResDTO serveAggregationSimpleResDTO = baseMapper.findServeDetailById(id);
        if(ObjectUtil.isNull(serveAggregationSimpleResDTO)){
            throw new ForbiddenOperationException("服务不存在");
        }
        return serveAggregationSimpleResDTO;
    }

    @Override
    public List<ServeAggregationTypeSimpleResDTO> serveTypeList(Long regionId) {
        //先区域校验
        Region region = regionMapper.selectById(regionId);
        if(ObjectUtil.isNull( region)||region.getActiveStatus()!=2){
            return Collections.emptyList();
        }
        List<ServeAggregationTypeSimpleResDTO> list = baseMapper.serveTypeList(regionId);
        return list;
    }

    //todo 非常重要，每日一练
    @Override
    public List<ServeSimpleResDTO> search(String cityCode, String keyword, Long serveTypeId) {
       //创建请求对象
        SearchRequest searchRequest = new SearchRequest("serve_aggregation");
        //创建查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termQuery("city_code",cityCode));
        if(ObjectUtil.isNotNull(serveTypeId)){
            boolQueryBuilder.must(QueryBuilders.termQuery("serve_type_id",serveTypeId));
        }
        if(ObjectUtil.isNotNull(keyword)){
            boolQueryBuilder.must(QueryBuilders.multiMatchQuery(keyword,"serve_item_name","serve_type_name"));
        }
        searchRequest.source().query(boolQueryBuilder);
        searchRequest.source().sort("serve_item_sort_num", SortOrder.ASC);
        //es查询
        SearchResponse response =null;
        try {
            response = esClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //处理查询结果
        long value = response.getHits().getTotalHits().value;
        if(value==0){
            return List.of();
        }
        SearchHit[] hits = response.getHits().getHits();
        List<ServeSimpleResDTO> collect = Arrays.stream(hits).map(hit ->
                JSONUtil.toBean(hit.getSourceAsString(), ServeSimpleResDTO.class)
        ).collect(Collectors.toList());
        return collect;
    }

    @Override
    public ServeAggregationResDTO findById(Long id) {
        return baseMapper.selectDetailById( id);
    }
}
